/**
 *   Copyright (c) fanguangping. All rights reserved.
 *   The use and distribution terms for this software are covered by the
 *   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
 *   which can be found in the file epl-v10.html at the root of this distribution.
 *   By using this software in any fashion, you are agreeing to be bound by
 * 	 the terms of this license.
 *   You must not remove this notice, or any other, from this software.
 **/
package org.jfunny.core;

import java.io.File;
import java.io.PushbackReader;
import java.io.StringReader;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import org.jfunny.core.dfa.DFA;
import org.jfunny.core.ds.Statement;
import org.jfunny.core.meta.Namespace;
import org.jfunny.core.parser.ParserFunny;
import org.jfunny.core.parser.ScannerFunny;
import org.jfunny.core.sys.SysLoader;
import org.jfunny.core.util.FileUtil;
import org.jfunny.core.util.StringUtil;

import clojure.lang.RT;
import clojure.lang.Var;

/**
 * 泛语言翻译器主函数
 * 
 * @author fanguangping
 *
 */
public class JFunny {
	public static final String suffix = ".funny";

	public static Set<Namespace> allNamespace = new HashSet<Namespace>();
	public static Set<Namespace> usedNamespace = new HashSet<Namespace>();

	public static void main(String[] args) {
		if (args.length != 1 && args.length != 2) {
			System.out.println();
			System.out.println("JFunny usage:> java JFunny <DirPath> [MainNamespace]");
			System.out.println();
		}
		else {
			try {
				//加载系统模板
				if (!SysLoader.loaded()) {
					SysLoader.load();
				}

				String dirPath = args[0];
				File dir = new File(dirPath);
				if (!dir.isDirectory()) {
					System.out.println("You should enter a directory to interprete!");
					return;
				}

				//加载泛语言源码及其命名空间
				cacheInit(dir);

				if (args.length == 1) {
					usedNamespace.addAll(allNamespace);
				}
				else {
					String mainNsName = args[1];
					Namespace mainNs = Namespace.findNamespace(allNamespace, mainNsName);

					//根据MainNs递归算出需要翻译的文件
					findNamespaces(mainNs);
				}

				for (Namespace ns : usedNamespace) {
					prepare(ns.getSource());
				}

				for (Namespace ns : usedNamespace) {
					interperte(ns);
				}

				if (args.length == 2) {
					String mainNsName = args[1];
					Namespace mainNs = Namespace.findNamespace(allNamespace, mainNsName);
					String filePath = getNewFilePath(mainNs);
					filePath = filePath.replace("\\", "/");

					Var function = getFunc("(defn my-load-file [] (load-file \"" + filePath + "\"))", "my-load-file");
					function.invoke();
				}

			}
			catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	private static Var getFunc(String code, String varname) throws Exception {
		Var func;
		String nsName = "ns_" + UUID.randomUUID().toString().replace("-", "_");
		clojure.lang.Symbol nsSym = clojure.lang.Symbol.create(nsName);
		clojure.lang.Namespace.findOrCreate(nsSym);
		try {
			clojure.lang.Compiler.load(new StringReader("(ns " + nsName + ")" + code));
			func = RT.var(nsName, varname);
			clojure.lang.Namespace.remove(nsSym);
		}
		finally {
			;
		}
		return func;
	}

	private static void cacheInit(File file) throws Exception {
		if (!file.isDirectory() && file.getAbsolutePath().endsWith(suffix)) {
			String funny = FileUtil.readTxt(file.getAbsolutePath());
			Namespace namespace = getNamespace(funny);
			namespace.setFile(file);
			namespace.setSource(funny);
			allNamespace.add(namespace);
		}
		else if (file.isDirectory()) {
			File[] subFiles = file.listFiles();
			for (File subFile : subFiles) {
				cacheInit(subFile);
			}
		}
	}

	private static void findNamespaces(Namespace ns) {
		usedNamespace.add(ns);
		for (Namespace used : ns.getUsed()) {
			Namespace usedNs = Namespace.findNamespace(allNamespace, used.getName());
			usedNamespace.add(usedNs);
			findNamespaces(used);
		}
	}

	private static void interperte(Namespace ns) throws Exception {
		String funny = ns.getSource();
		String result = parse(ns, funny);
		if (!ns.isTemplateSpace()) {
			String newFilePath = getNewFilePath(ns);
			FileUtil.writeTxt(newFilePath, result);
		}
	}

	private static String getNewFilePath(Namespace ns) {
		String newFilePath = StringUtil.substringBefore(ns.getFile().getAbsolutePath(), suffix)
				+ SysLoader.resultSuffix;
		return newFilePath;
	}

	/**
	 * 获得命名空间
	 * @param funny
	 * @return
	 * @throws Exception
	 */
	private static Namespace getNamespace(String funny) throws Exception {
		ParserFunny parser = new ParserFunny(new ScannerFunny(new PushbackReader(new StringReader(funny))));

		if (parser.hasNext()) {
			Statement s = parser.next();
			Namespace ns = DFA.getInstance().getNamespace(s);
			return ns;
		}

		return null;
	}

	private static void prepare(String funny) throws Exception {
		ParserFunny parser = new ParserFunny(new ScannerFunny(new PushbackReader(new StringReader(funny))));

		while (parser.hasNext()) {
			Statement s = parser.next();
			DFA.getInstance().prepare(s);
		}
	}

	public static String parse(Namespace ns, String funny) throws Exception {
		ParserFunny parser = new ParserFunny(new ScannerFunny(new PushbackReader(new StringReader(funny))));

		StringBuffer buffer = new StringBuffer();
		while (parser.hasNext()) {
			Statement s = parser.next();

			String result = DFA.getInstance().match(ns, s);

			buffer.append(result);
			buffer.append("\n");
		}

		return buffer.toString();
	}
}
